home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / XMLRPCLIB.PY < prev    next >
Encoding:
Python Source  |  2000-07-12  |  19.4 KB  |  664 lines

  1. #
  2. # XML-RPC CLIENT LIBRARY
  3. # $Id$
  4. #
  5. # an XML-RPC client interface for Python.
  6. #
  7. # the marshalling and response parser code can also be used to
  8. # implement XML-RPC servers.
  9. #
  10. # Notes:
  11. # this version uses the sgmlop XML parser, if installed.  this is
  12. # typically 10-15x faster than using Python's standard XML parser.
  13. #
  14. # you can get the sgmlop distribution from:
  15. #
  16. #    http://www.pythonware.com/madscientist
  17. #
  18. # also note that this version is designed to work with Python 1.5.1
  19. # or newer.  it doesn't use any 1.5.2-specific features.
  20. #
  21. # History:
  22. # 1999-01-14 fl  Created
  23. # 1999-01-15 fl  Changed dateTime to use localtime
  24. # 1999-01-16 fl  Added Binary/base64 element, default to RPC2 service
  25. # 1999-01-19 fl  Fixed array data element (from Skip Montanaro)
  26. # 1999-01-21 fl  Fixed dateTime constructor, etc.
  27. # 1999-02-02 fl  Added fault handling, handle empty sequences, etc.
  28. # 1999-02-10 fl  Fixed problem with empty responses (from Skip Montanaro)
  29. # 1999-06-20 fl  Speed improvements, pluggable XML parsers and HTTP transports
  30. #
  31. # Copyright (c) 1999 by Secret Labs AB.
  32. # Copyright (c) 1999 by Fredrik Lundh.
  33. #
  34. # fredrik@pythonware.com
  35. # http://www.pythonware.com
  36. #
  37. # --------------------------------------------------------------------
  38. # The XML-RPC client interface is
  39. # Copyright (c) 1999 by Secret Labs AB
  40. # Copyright (c) 1999 by Fredrik Lundh
  41. # By obtaining, using, and/or copying this software and/or its
  42. # associated documentation, you agree that you have read, understood,
  43. # and will comply with the following terms and conditions:
  44. #
  45. # Permission to use, copy, modify, and distribute this software and
  46. # its associated documentation for any purpose and without fee is
  47. # hereby granted, provided that the above copyright notice appears in
  48. # all copies, and that both that copyright notice and this permission
  49. # notice appear in supporting documentation, and that the name of
  50. # Secret Labs AB or the author not be used in advertising or publicity
  51. # pertaining to distribution of the software without specific, written
  52. # prior permission.
  53. #
  54. # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  55. # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
  56. # ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
  57. # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  58. # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  59. # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
  60. # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
  61. # OF THIS SOFTWARE.
  62. # --------------------------------------------------------------------
  63.  
  64. import string, time
  65. import urllib, xmllib
  66. from types import *
  67. from cgi import escape
  68.  
  69. try:
  70.     import sgmlop
  71. except ImportError:
  72.     sgmlop = None # accelerator not available
  73.  
  74. __version__ = "0.9.8"
  75.  
  76.  
  77. # --------------------------------------------------------------------
  78. # Exceptions
  79.  
  80. class Error:
  81.     # base class for client errors
  82.     pass
  83.  
  84. class ProtocolError(Error):
  85.     # indicates an HTTP protocol error
  86.     def __init__(self, url, errcode, errmsg, headers):
  87.         self.url = url
  88.         self.errcode = errcode
  89.         self.errmsg = errmsg
  90.         self.headers = headers
  91.     def __repr__(self):
  92.         return (
  93.             "<ProtocolError for %s: %s %s>" %
  94.             (self.url, self.errcode, self.errmsg)
  95.             )
  96.  
  97. class ResponseError(Error):
  98.     # indicates a broken response package
  99.     pass
  100.  
  101. class Fault(Error):
  102.     # indicates a XML-RPC fault package
  103.     def __init__(self, faultCode, faultString, **extra):
  104.         self.faultCode = faultCode
  105.         self.faultString = faultString
  106.     def __repr__(self):
  107.         return (
  108.             "<Fault %s: %s>" %
  109.             (self.faultCode, repr(self.faultString))
  110.             )
  111.  
  112.  
  113. # --------------------------------------------------------------------
  114. # Special values
  115.  
  116. # boolean wrapper
  117. # (you must use True or False to generate a "boolean" XML-RPC value)
  118.  
  119. class Boolean:
  120.  
  121.     def __init__(self, value = 0):
  122.         self.value = (value != 0)
  123.  
  124.     def encode(self, out):
  125.         out.write("<value><boolean>%d</boolean></value>\n" % self.value)
  126.  
  127.     def __repr__(self):
  128.         if self.value:
  129.             return "<Boolean True at %x>" % id(self)
  130.         else:
  131.             return "<Boolean False at %x>" % id(self)
  132.  
  133.     def __int__(self):
  134.         return self.value
  135.  
  136.     def __nonzero__(self):
  137.         return self.value
  138.  
  139. True, False = Boolean(1), Boolean(0)
  140.  
  141. #
  142. # dateTime wrapper
  143. # (wrap your iso8601 string or time tuple or localtime time value in
  144. # this class to generate a "dateTime.iso8601" XML-RPC value)
  145.  
  146. class DateTime:
  147.  
  148.     def __init__(self, value = 0):
  149.         t = type(value)
  150.         if t is not StringType:
  151.             if t is not TupleType:
  152.                 value = time.localtime(value)
  153.             value = time.strftime("%Y%m%dT%H:%M:%S", value)
  154.         self.value = value
  155.  
  156.     def __repr__(self):
  157.         return "<DateTime %s at %x>" % (self.value, id(self))
  158.  
  159.     def decode(self, data):
  160.         self.value = string.strip(data)
  161.  
  162.     def encode(self, out):
  163.         out.write("<value><dateTime.iso8601>")
  164.         out.write(self.value)
  165.         out.write("</dateTime.iso8601></value>\n")
  166.  
  167. #
  168. # binary data wrapper (NOTE: this is an extension to Userland's
  169. # XML-RPC protocol! only for use with compatible servers!)
  170.  
  171. class Binary:
  172.  
  173.     def __init__(self, data=None):
  174.         self.data = data
  175.  
  176.     def decode(self, data):
  177.         import base64
  178.         self.data = base64.decodestring(data)
  179.  
  180.     def encode(self, out):
  181.         import base64, StringIO
  182.         out.write("<value><base64>\n")
  183.         base64.encode(StringIO.StringIO(self.data), out)
  184.         out.write("</base64></value>\n")
  185.  
  186. WRAPPERS = DateTime, Binary, Boolean
  187.  
  188.  
  189. # --------------------------------------------------------------------
  190. # XML parsers
  191.  
  192. if sgmlop:
  193.  
  194.     class FastParser:
  195.         # sgmlop based XML parser.  this is typically 15x faster
  196.         # than SlowParser...
  197.  
  198.         def __init__(self, target):
  199.  
  200.             # setup callbacks
  201.             self.finish_starttag = target.start
  202.             self.finish_endtag = target.end
  203.             self.handle_data = target.data
  204.  
  205.             # activate parser
  206.             self.parser = sgmlop.XMLParser()
  207.             self.parser.register(self)
  208.             self.feed = self.parser.feed
  209.             self.entity = {
  210.                 "amp": "&", "gt": ">", "lt": "<",
  211.                 "apos": "'", "quot": '"'
  212.                 }
  213.  
  214.         def close(self):
  215.             try:
  216.                 self.parser.close()
  217.             finally:
  218.                 self.parser = None # nuke circular reference
  219.  
  220.         def handle_entityref(self, entity):
  221.             # <string> entity
  222.             try:
  223.                 self.handle_data(self.entity[entity])
  224.             except KeyError:
  225.                 self.handle_data("&%s;" % entity)
  226.  
  227. else:
  228.  
  229.     FastParser = None
  230.  
  231. class SlowParser(xmllib.XMLParser):
  232.     # slow but safe standard parser, based on the XML parser in
  233.     # Python's standard library
  234.  
  235.     def __init__(self, target):
  236.         self.unknown_starttag = target.start
  237.         self.handle_data = target.data
  238.         self.unknown_endtag = target.end
  239.         xmllib.XMLParser.__init__(self)
  240.  
  241.  
  242. # --------------------------------------------------------------------
  243. # XML-RPC marshalling and unmarshalling code
  244.  
  245. class Marshaller:
  246.     """Generate an XML-RPC params chunk from a Python data structure"""
  247.  
  248.     # USAGE: create a marshaller instance for each set of parameters,
  249.     # and use "dumps" to convert your data (represented as a tuple) to
  250.     # a XML-RPC params chunk.  to write a fault response, pass a Fault
  251.     # instance instead.  you may prefer to use the "dumps" convenience
  252.     # function for this purpose (see below).
  253.  
  254.     # by the way, if you don't understand what's going on in here,
  255.     # that's perfectly ok.
  256.  
  257.     def __init__(self):
  258.         self.memo = {}
  259.         self.data = None
  260.  
  261.     dispatch = {}
  262.  
  263.     def dumps(self, values):
  264.         self.__out = []
  265.         self.write = write = self.__out.append
  266.         if isinstance(values, Fault):
  267.             # fault instance
  268.             write("<fault>\n")
  269.             self.__dump(vars(values))
  270.             write("</fault>\n")
  271.         else:
  272.             # parameter block
  273.             write("<params>\n")
  274.             for v in values:
  275.                 write("<param>\n")
  276.                 self.__dump(v)
  277.                 write("</param>\n")
  278.             write("</params>\n")
  279.         result = string.join(self.__out, "")
  280.         del self.__out, self.write # don't need this any more
  281.         return result
  282.  
  283.     def __dump(self, value):
  284.         try:
  285.             f = self.dispatch[type(value)]
  286.         except KeyError:
  287.             raise TypeError, "cannot marshal %s objects" % type(value)
  288.         else:
  289.             f(self, value)
  290.  
  291.     def dump_int(self, value):
  292.         self.write("<value><int>%s</int></value>\n" % value)
  293.     dispatch[IntType] = dump_int
  294.  
  295.     def dump_double(self, value):
  296.         self.write("<value><double>%s</double></value>\n" % value)
  297.     dispatch[FloatType] = dump_double
  298.  
  299.     def dump_string(self, value):
  300.         self.write("<value><string>%s</string></value>\n" % escape(value))
  301.     dispatch[StringType] = dump_string
  302.  
  303.     def container(self, value):
  304.         if value:
  305.             i = id(value)
  306.             if self.memo.has_key(i):
  307.                 raise TypeError, "cannot marshal recursive data structures"
  308.             self.memo[i] = None
  309.  
  310.     def dump_array(self, value):
  311.         self.container(value)
  312.         write = self.write
  313.         write("<value><array><data>\n")
  314.         for v in value:
  315.             self.__dump(v)
  316.         write("</data></array></value>\n")
  317.     dispatch[TupleType] = dump_array
  318.     dispatch[ListType] = dump_array
  319.  
  320.     def dump_struct(self, value):
  321.         self.container(value)
  322.         write = self.write
  323.         write("<value><struct>\n")
  324.         for k, v in value.items():
  325.             write("<member>\n")
  326.             if type(k) is not StringType:
  327.                 raise TypeError, "dictionary key must be string"
  328.             write("<name>%s</name>\n" % escape(k))
  329.             self.__dump(v)
  330.             write("</member>\n")
  331.         write("</struct></value>\n")
  332.     dispatch[DictType] = dump_struct
  333.  
  334.     def dump_instance(self, value):
  335.         # check for special wrappers
  336.         if value.__class__ in WRAPPERS:
  337.             value.encode(self)
  338.         else:
  339.             # store instance attributes as a struct (really?)
  340.             self.dump_struct(value.__dict__)
  341.     dispatch[InstanceType] = dump_instance
  342.  
  343.  
  344. class Unmarshaller:
  345.  
  346.     # unmarshal an XML-RPC response, based on incoming XML event
  347.     # messages (start, data, end).  call close to get the resulting
  348.     # data structure
  349.  
  350.     # note that this reader is fairly tolerant, and gladly accepts
  351.     # bogus XML-RPC data without complaining (but not bogus XML).
  352.  
  353.     # and again, if you don't understand what's going on in here,
  354.     # that's perfectly ok.
  355.  
  356.     def __init__(self):
  357.         self._type = None
  358.         self._stack = []
  359.         self._marks = []
  360.         self._data = []
  361.         self._methodname = None
  362.         self.append = self._stack.append
  363.  
  364.     def close(self):
  365.         # return response code and the actual response
  366.         if self._type is None or self._marks:
  367.             raise ResponseError()
  368.         if self._type == "fault":
  369.             raise apply(Fault, (), self._stack[0])
  370.         return tuple(self._stack)
  371.  
  372.     def getmethodname(self):
  373.         return self._methodname
  374.  
  375.     #
  376.     # event handlers
  377.  
  378.     def start(self, tag, attrs):
  379.         # prepare to handle this element
  380.         if tag in ("array", "struct"):
  381.             self._marks.append(len(self._stack))
  382.         self._data = []
  383.         self._value = (tag == "value")
  384.  
  385.     def data(self, text):
  386.         self._data.append(text)
  387.  
  388.     dispatch = {}
  389.  
  390.     def end(self, tag):
  391.         # call the appropriate end tag handler
  392.         try:
  393.             f = self.dispatch[tag]
  394.         except KeyError:
  395.             pass # unknown tag ?
  396.         else:
  397.             return f(self)
  398.  
  399.     #
  400.     # element decoders
  401.  
  402.     def end_boolean(self, join=string.join):
  403.         value = join(self._data, "")
  404.         if value == "0":
  405.             self.append(False)
  406.         elif value == "1":
  407.             self.append(True)
  408.         else:
  409.             raise TypeError, "bad boolean value"
  410.         self._value = 0
  411.     dispatch["boolean"] = end_boolean
  412.  
  413.     def end_int(self, join=string.join):
  414.         self.append(int(join(self._data, "")))
  415.         self._value = 0
  416.     dispatch["i4"] = end_int
  417.     dispatch["int"] = end_int
  418.  
  419.     def end_double(self, join=string.join):
  420.         self.append(float(join(self._data, "")))
  421.         self._value = 0
  422.     dispatch["double"] = end_double
  423.  
  424.     def end_string(self, join=string.join):
  425.         self.append(join(self._data, ""))
  426.         self._value = 0
  427.     dispatch["string"] = end_string
  428.     dispatch["name"] = end_string # struct keys are always strings
  429.  
  430.     def end_array(self):
  431.         mark = self._marks[-1]
  432.         del self._marks[-1]
  433.         # map arrays to Python lists
  434.         self._stack[mark:] = [self._stack[mark:]]
  435.         self._value = 0
  436.     dispatch["array"] = end_array
  437.  
  438.     def end_struct(self):
  439.         mark = self._marks[-1]
  440.         del self._marks[-1]
  441.         # map structs to Python dictionaries
  442.         dict = {}
  443.         items = self._stack[mark:]
  444.         for i in range(0, len(items), 2):
  445.             dict[items[i]] = items[i+1]
  446.         self._stack[mark:] = [dict]
  447.         self._value = 0
  448.     dispatch["struct"] = end_struct
  449.  
  450.     def end_base64(self, join=string.join):
  451.         value = Binary()
  452.         value.decode(join(self._data, ""))
  453.         self.append(value)
  454.         self._value = 0
  455.     dispatch["base64"] = end_base64
  456.  
  457.     def end_dateTime(self, join=string.join):
  458.         value = DateTime()
  459.         value.decode(join(self._data, ""))
  460.         self.append(value)
  461.     dispatch["dateTime.iso8601"] = end_dateTime
  462.  
  463.     def end_value(self):
  464.         # if we stumble upon an value element with no internal
  465.         # elements, treat it as a string element
  466.         if self._value:
  467.             self.end_string()
  468.     dispatch["value"] = end_value
  469.  
  470.     def end_params(self):
  471.         self._type = "params"
  472.     dispatch["params"] = end_params
  473.  
  474.     def end_fault(self):
  475.         self._type = "fault"
  476.     dispatch["fault"] = end_fault
  477.  
  478.     def end_methodName(self, join=string.join):
  479.         self._methodname = join(self._data, "")
  480.     dispatch["methodName"] = end_methodName
  481.  
  482.  
  483. # --------------------------------------------------------------------
  484. # convenience functions
  485.  
  486. def getparser():
  487.     # get the fastest available parser, and attach it to an
  488.     # unmarshalling object.  return both objects.
  489.     target = Unmarshaller()
  490.     if FastParser:
  491.         return FastParser(target), target
  492.     return SlowParser(target), target
  493.  
  494. def dumps(params, methodname=None, methodresponse=None):
  495.     # convert a tuple or a fault object to an XML-RPC packet
  496.  
  497.     assert type(params) == TupleType or isinstance(params, Fault),\
  498.            "argument must be tuple or Fault instance"
  499.  
  500.     m = Marshaller()
  501.     data = m.dumps(params)
  502.  
  503.     # standard XML-RPC wrappings
  504.     if methodname:
  505.         # a method call
  506.         data = (
  507.             "<?xml version='1.0'?>\n"
  508.             "<methodCall>\n"
  509.             "<methodName>%s</methodName>\n"
  510.             "%s\n"
  511.             "</methodCall>\n" % (methodname, data)
  512.             )
  513.     elif methodresponse or isinstance(params, Fault):
  514.         # a method response
  515.         data = (
  516.             "<?xml version='1.0'?>\n"
  517.             "<methodResponse>\n"
  518.             "%s\n"
  519.             "</methodResponse>\n" % data
  520.             )
  521.     return data
  522.  
  523. def loads(data):
  524.     # convert an XML-RPC packet to data plus a method name (None
  525.     # if not present).  if the XML-RPC packet represents a fault
  526.     # condition, this function raises a Fault exception.
  527.     p, u = getparser()
  528.     p.feed(data)
  529.     p.close()
  530.     return u.close(), u.getmethodname()
  531.  
  532.  
  533. # --------------------------------------------------------------------
  534. # request dispatcher
  535.  
  536. class _Method:
  537.     # some magic to bind an XML-RPC method to an RPC server.
  538.     # supports "nested" methods (e.g. examples.getStateName)
  539.     def __init__(self, send, name):
  540.         self.__send = send
  541.         self.__name = name
  542.     def __getattr__(self, name):
  543.         return _Method(self.__send, "%s.%s" % (self.__name, name))
  544.     def __call__(self, *args):
  545.         return self.__send(self.__name, args)
  546.  
  547.  
  548. class Transport:
  549.     """Handles an HTTP transaction to an XML-RPC server"""
  550.  
  551.     # client identifier (may be overridden)
  552.     user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__
  553.  
  554.     def request(self, host, handler, request_body):
  555.         # issue XML-RPC request
  556.  
  557.         import httplib
  558.         h = httplib.HTTP(host)
  559.         h.putrequest("POST", handler)
  560.  
  561.         # required by HTTP/1.1
  562.         h.putheader("Host", host)
  563.  
  564.         # required by XML-RPC
  565.         h.putheader("User-Agent", self.user_agent)
  566.         h.putheader("Content-Type", "text/xml")
  567.         h.putheader("Content-Length", str(len(request_body)))
  568.  
  569.         h.endheaders()
  570.  
  571.         if request_body:
  572.             h.send(request_body)
  573.  
  574.         errcode, errmsg, headers = h.getreply()
  575.  
  576.         if errcode != 200:
  577.             raise ProtocolError(
  578.                 host + handler,
  579.                 errcode, errmsg,
  580.                 headers
  581.                 )
  582.  
  583.         return self.parse_response(h.getfile())
  584.  
  585.     def parse_response(self, f):
  586.         # read response from input file, and parse it
  587.  
  588.         p, u = getparser()
  589.  
  590.         while 1:
  591.             response = f.read(1024)
  592.             if not response:
  593.                 break
  594.             p.feed(response)
  595.  
  596.         f.close()
  597.         p.close()
  598.  
  599.         return u.close()
  600.  
  601.  
  602. class Server:
  603.     """Represents a connection to an XML-RPC server"""
  604.  
  605.     def __init__(self, uri, transport=None):
  606.         # establish a "logical" server connection
  607.  
  608.         # get the url
  609.         type, uri = urllib.splittype(uri)
  610.         if type not in ("http", "https"):
  611.             raise IOError, "unsupported XML-RPC protocol"
  612.         self.__host, self.__handler = urllib.splithost(uri)
  613.         if not self.__handler:
  614.             self.__handler = "/RPC2"
  615.  
  616.         if transport is None:
  617.             transport = Transport()
  618.         self.__transport = transport
  619.  
  620.     def __request(self, methodname, params):
  621.         # call a method on the remote server
  622.  
  623.         request = dumps(params, methodname)
  624.  
  625.         response = self.__transport.request(
  626.             self.__host,
  627.             self.__handler,
  628.             request
  629.             )
  630.  
  631.         if len(response) == 1:
  632.             return response[0]
  633.  
  634.         return response
  635.  
  636.     def __repr__(self):
  637.         return (
  638.             "<Server proxy for %s%s>" %
  639.             (self.__host, self.__handler)
  640.             )
  641.  
  642.     __str__ = __repr__
  643.  
  644.     def __getattr__(self, name):
  645.         # magic method dispatcher
  646.         return _Method(self.__request, name)
  647.  
  648.  
  649. if __name__ == "__main__":
  650.  
  651.     # simple test program (from the XML-RPC specification)
  652.     # server = Server("http://localhost:8000") # local server
  653.  
  654.     server = Server("http://betty.userland.com")
  655.  
  656.     print server
  657.  
  658.     try:
  659.         print server.examples.getStateName(41)
  660.     except Error, v:
  661.         print "ERROR", v
  662.